
//
// 101118 - AH - Created.
// 101122 - AH - save().
// 101123 - AH - SoekratEngine(SoekratDatabase).
// 101206 - AH - getForest().
// 101228 - AH - Java doc todo and error found by java doc generator.
// 110111a- AH - Fires RetrievalPerformedEvent.
// 110111b- AH - (Fantoft) getRetrievalEventTree().
// 110111c- AH - (Fantoft) getMotor().
// 110201 - AH - (Hector) Motor now accepts this as parameter in constructors.
// 110202 - AH - eventsHappened(List<HistoricalEvent>).
// 110216 - AH - getNetwork().
// 110220 - AH - ScanAndPaint now accepts new type Input, used to accept String.
// 110305 - AH - (Fantoft) ScanAndPaint now accepts new type Lines, used to 
//               accept Input.
// 110306 - AH - (Fantoft Club) Removed not needed method setWheel() and added
//               getSpareWheel().
// 110310 - AH - Fixed so no arg. constructor creates wheels with empty 
//               ontologies and a motor.
// 110405 - AH - pit property.
// 110423a- AH - Makes getWheel() and getSpareWheel() create wheels if has 
//               none.
// 110423b- AH - Removed direct access to ontologies, must go through wheels.
// 110621 - AH - (Zen *) Saves conversation in scanAndPaint(Lines).
// 110622 - AH - (Bybanen *) Now scanAndPaint(Lines) set Lines property om
//               return value.
// 110625 - AH - (Fotballpuben *) Now passes this to method of SoekratData that
//               save chat.
// 110702 - AH - (Garage) Removed Rethor and added some missing java doc.
// 120207 - AH . (Garage) addPassingStrategy(PassingStrategy),
//               getPassingStrategy(PassingStrategy) and 
//               getPassingStrategy(Class<?>).
// 120220 - AH - (Pygmalion) Made AttributObject because that can be useful
//               to share common information in a semantic machine who use it.
// 120224 - AH - (Kryddar *) Changes related to that the ScanPaintResult is
//               replaced by ConversationElement.
//

package org.soekrat.engine;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.soekrat.AttributeObject;
import org.soekrat.engine.event.RetrievalPerformedEvent;
import org.soekrat.engine.event.RetrievalPerformedEventListener;
import org.soekrat.engine.event.SoekratChangeEvent;
import org.soekrat.engine.event.SoekratChangeListener;
import org.soekrat.engine.io.SoekratData;
import org.soekrat.engine.io.SoekratDatabase;
import org.soekrat.engine.io.XmlFileSoekratDataBase;
import org.soekrat.engine.motor.ConversationElement;
import org.soekrat.engine.motor.Motor;
import org.soekrat.engine.motor.passingstrategies.CheckForContentPassingStrategy;
import org.soekrat.engine.motor.passingstrategies.PassingStrategy;
import org.soekrat.engine.motor.passingstrategies.WheelPassingStrategy;
import org.soekrat.engine.pit.Pit;
import org.soekrat.engine.sparewheel.SpareWheel;
import org.soekrat.engine.wheel.Wheel;
import org.soekrat.engine.wheel.retrieval.RetrievalEventTree;

/**
 * <p>
 *   Facade to the semantic social machine that is Soekrat.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC)  
 */
public final class SoekratEngine extends AttributeObject //implements SoekratData
{
  private transient List<RetrievalPerformedEventListener> m_rpels=null;
  
  private transient List<SoekratChangeListener> m_listeners=null;
  
  private SoekratDatabase m_database=null;
  
  private Wheel m_wheel=null;
  
  private SpareWheel m_spareWheel=null;
  
  private Motor m_motor=null;
  
  private final Map<String, PassingStrategy> m_passingStrategies=
    new HashMap<String, PassingStrategy>();
  
  /**
   * <p>
   *   Constructor.
   * </p>
   */
  public SoekratEngine()
  {
    m_wheel=new Wheel();
    m_spareWheel=new SpareWheel();
    m_motor=new Motor(this);
    
    addPassingStrategy(new CheckForContentPassingStrategy());
    addPassingStrategy(new WheelPassingStrategy());
  }
  
  /**
   * <p>
   *   Add
   *   {@link PassingStrategy}.
   * </p>
   * @param ps {@code PassingStrategy}.
   */
  public void addPassingStrategy(PassingStrategy ps)
  {
    if (ps==null)
    {
      throw new NullPointerException("ps");
    }
    
    String name=ps.getClass().getName();
    if (m_passingStrategies.containsKey(name))
    {
      throw new IllegalArgumentException("has strategy : "+name);
    }
    
    m_passingStrategies.put(name, ps);
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link PassingStrategy}.
   * </p>
   * @param clazz {@link Class} of 
   *              {@link PassingStrategy} to get.
   * @return {@link PassingStrategy} or {@code null} if not found.
   */
  public PassingStrategy getPassingStrategy(Class<?> clazz)
  {
    if (clazz==null)
    {
      throw new NullPointerException("clazz");
    }
    
    return getPassingStrategy(clazz.getName());
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link PassingStrategy}.
   * </p>
   * @param name Name (qualified class name) of 
   *             {@link PassingStrategy} to get.
   * @return {@link PassingStrategy} or {@code null} if not found.
   */
  public PassingStrategy getPassingStrategy(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    return m_passingStrategies.get(name);
  }
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param sd {@link SoekratDatabase} to load data from and save to.
   * @throws IOException If fails loading from {@code sd}.
   */
  public SoekratEngine(SoekratDatabase sd) throws IOException
  {
    if (sd==null)
    {
      throw new NullPointerException("sd");
    }
    
    m_database=sd;
    
    Map<String, SoekratData> data=sd.load();
    
    SoekratData wheelData=data.get("soekrat");
    if (wheelData!=null)
    {
      m_wheel=new Wheel(wheelData);      
    }
    else
    {
      m_wheel=new Wheel();
    }
    
    SoekratData spareData=data.get("spare");
    if (spareData!=null)
    {
      m_spareWheel=new SpareWheel(spareData);
    }
    else
    {
      m_spareWheel=new SpareWheel();
    }
    
    m_motor=new Motor(this);
  }
  
  /**
   * <p>
   *   Sets the title of conversation.
   * </p>
   * @param title Title.
   */
  public void setConversationTitle(String title)
  {
    if (m_wheel!=null)
    {
      m_wheel.setTitle(title);
      fireChange();
    }
  }
  
  /**
   * <p>
   *   Gets the title of the conversation.
   * </p>
   * @return Title.
   */
  public String getConversationTitle()
  {
    if (m_wheel!=null)
    {
      return m_wheel.getTitle();
    }
    return "no title";
  }
  
  /**
   * <p>
   *   Saves.
   * </p>
   * @throws IOException If fails.
   */
  public void save() throws IOException 
  {
    if (m_database==null) m_database=new XmlFileSoekratDataBase();
    
    m_database.save(m_wheel, m_spareWheel);
  }
  
  /**
   * <p>
   *   Gets
   *   {@link Pit}.
   * </p>
   * @return {@code Pit}.
   */
  public Pit getPit(){ return getWheel().getPit(); }
  
  /**
   * <p>
   *   Gets 
   *   {@link Motor}.
   * </p>
   * @return {@link Motor}.
   */
  public Motor getMotor(){ return m_motor; }
  
  /**
   * <p>
   *   Gets the
   *   {@link SpareWheel}.
   * </p>
   * @return {@code SpareWheel}.
   */
  public SpareWheel getSpareWheel()
  {
    if (m_spareWheel==null) m_spareWheel=new SpareWheel();
    return m_spareWheel; 
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link Wheel}.
   * </p>
   * @return {@code Wheel}.
   */
  public Wheel getWheel()
  { 
    if (m_wheel==null) m_wheel=new Wheel();
    return m_wheel;
  }
 
  /**
   * <p>
   *   Makes keywords of the input from end user and retrieve from the
   *   {@link Wheel}. 
   * </p>
   * @param ce    {@link ConversationElement} for the conversation.
   * @return Retrieved objects.
   */
  public void scanAndPaint(ConversationElement ce)
  {
    if (ce==null)
    {
      throw new NullPointerException("ce");
    }
    
    m_motor.spin(ce);
    
    try
    {
      m_database.save(ce, this);
    }
    catch (IOException iox)
    {
      iox.printStackTrace();
    }
    
    fireRetrievalPerformedEvent();
  }
  
  /**
   * <p>
   *   Gets the
   *   {@link RetrievalEventTree}.
   * </p>
   * @return {@code RetrievalEventTree}.
   */
  public RetrievalEventTree getRetrievalEventTree()
  { 
    return m_motor.getRetrievalEventTree(); 
  }
  
  /**
   * <p>
   *   Adds 
   *   {@link RetrievalPerformedEventListener}.
   * </p>
   * @param l Listener to add.
   */
  public synchronized void addRetrievalPerformedEventListener(
    RetrievalPerformedEventListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_rpels==null) m_rpels=new ArrayList<RetrievalPerformedEventListener>();
    if (!m_rpels.contains(l)) m_rpels.add(l);
  }
  
  /**
   * <p>
   *   Removes 
   *   {@link RetrievalPerformedEventListener}.
   * </p>
   * @param l Listener to remove.
   */
  public synchronized void removeRetrievalPerformedEventListener(
    RetrievalPerformedEventListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_rpels==null) return;
    m_rpels.remove(l);
  }
  
  private final RetrievalPerformedEvent RPE=new RetrievalPerformedEvent(this);
  
  // Fires RetrievalPerformedEvent.
  private void fireRetrievalPerformedEvent()
  {
    if (m_rpels==null) return;
    
    RetrievalPerformedEventListener[] l=
      RetrievalPerformedEventListener.EMPTY_ARRAY;
    synchronized(this){ l=m_rpels.toArray(l); }
    for (int i=l.length-1; i>=0; i--) l[i].retrievalPerformed(RPE);
  }
  
  /**
   * <p>
   *   Adds
   *   {@link SoekratChangeListener}.
   * </p>
   * @param l Listener to add.
   */
  public void addSoekratChangeListener(SoekratChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) m_listeners=new ArrayList<SoekratChangeListener>();
    if (!m_listeners.contains(l)) m_listeners.add(l);
  }
  
  /**
   * <p>
   *   Removes
   *   {@link SoekratChangeListener}.
   * </p>
   * @param l Listener to remove.
   */
  public void removeSoekratChangeListener(SoekratChangeListener l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    if (m_listeners==null) return;
    m_listeners.remove(l);
  }
  
  private final SoekratChangeEvent m_ce=new SoekratChangeEvent(this);
  
  private void fireChange()
  {
    if (m_listeners==null) return;
    SoekratChangeListener[] l=SoekratChangeListener.E_ARRAY;
    synchronized(this){ l=m_listeners.toArray(l); }
    for (SoekratChangeListener curr : l) curr.changed(m_ce); 
  }
    
}
